﻿/************************************************************************
 * CoreDES - A small and core library for discrete event systems
 * Copyright (C) 2012-2013 Ali Khalili (khalili_DOT_ir_@SIGN_gmail_DOT_com)
 * **********************************************************************
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License (LGPL) published 
 * by the Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version. This program is distributed without any
 * warranty. See the GNU Lesser General Public License for more details. 
 * ***********************************************************************
 * File         :     CoreDES\Utils\XmlHelper.cs
 * Description  :     To read and write models from/to XML format
 * Change log   :     -2010: initial version (originaly belongs to SimGine Library)
 *                    (c) Amir Jalaly (amir.jalaly@gmail.com)
 * To Do        :
 ************************************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;

namespace CoreDES.Utils
{
    /// <summary>
    /// A helper class to read and write XML databases. An XML database does not have to have a unique root!
    /// </summary>
    public class XmlHelper
    {
        #region Internal defenitions
        
        //** CHECK!: is it required???
        SortedList<string, string> attr = new SortedList<string, string>();
        SortedList<string, List<XmlRow>> tables = new SortedList<string, List<XmlRow>>(new XMLNameComparer());

        class XMLNameComparer : IComparer<string>
        {
            #region IComparer<string> Members

            public int Compare(string x, string y)
            {
                return x.ToLower().CompareTo(y.ToLower());
            }

            #endregion
        }
        #endregion


        public class XmlRowNotFoundExeption : Exception
        {
            public XmlRowNotFoundExeption(string rowname)
                : base(rowname + " not Found!")
            {

            }
        }


        /// <summary>
        /// An XML row which is defined recursively and includes:
        /// - A name
        /// - A couple (possibily zero) of attributes
        /// - A couple (possibily zero) of XML rows
        /// </summary>
        public class XmlRow
        {
            /// <summary>
            /// Name of XML row
            /// </summary>
            public string name;

            /// <summary>
            /// List of attributes and their value
            /// </summary>
            SortedList<string, string> attr = new SortedList<string, string>(new XMLNameComparer());

            /// <summary>
            /// inside row values
            /// </summary>
            SortedList<string, List<XmlRow>> tables = new SortedList<string, List<XmlRow>>(new XMLNameComparer());

            /// <summary>
            /// inside string value
            /// </summary>
            string value = "";

            /// <summary>
            /// get the inside string value
            /// </summary>
            public string Value
            {
                get
                {
                    return value;
                }
                set
                {
                    this.value = value;
                }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="name"></param>
            /// <param name="i"></param>
            /// <returns></returns>
            public XmlRow this[string name, int i]
            {
                get
                {
                    if (tables.ContainsKey(name) &&
                        tables[name].Count > i && i >= 0)
                        return tables[name][i];
                    return null;
                }
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="name"></param>
            /// <returns></returns>
            public XmlRow this[string name]
            {
                get
                {
                    if (tables.ContainsKey(name) &&
                        tables[name].Count > 0)
                        return tables[name][0];
                    //throw new XmlRowNotFoundExeption(name);
                    return null;
                }
                set
                {
                    if (tables.ContainsKey(name) &&
                        tables[name].Count > 0)
                    {
                        tables[name][0].value = value.value;
                    }
                    else
                    {
                        AddXmlRow(name);
                        this[name] = value.value;
                    }
                }
            }
        
            /// <summary>
            /// 
            /// </summary>
            /// <param name="name"></param>
            /// <returns></returns>
            public bool Contain(string name)
            {
                return tables.ContainsKey(name) && tables[name].Count > 0;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="key"></param>
            /// <returns></returns>
            public string GetAttribute(string key)
            {
                if (attr.ContainsKey(key))
                    return attr[key];
                return "";
            }



            public SortedList<string, string> Attributes
            {
                get
                {
                    return attr;
                }
            }


            static public implicit operator string(XmlRow row)
            {
                if (row == null)
                    return null;
                return row.value;
            }


            static public implicit operator XmlRow(string value)
            {
                XmlRow r = new XmlRow();
                r.value = value;
                return r;
            }

            public string NodeName
            {
                get
                {
                    return name;
                }
                set
                {
                    name = value;
                }
            }

            /// <summary>
            /// Add a row
            /// </summary>
            /// <param name="type"></param>
            /// <returns></returns>
            public XmlRow AddXmlRow(string type)
            {
                XmlRow row = new XmlRow();
                row.name = type;
                if (!tables.ContainsKey(type))
                    tables[type] = new List<XmlRow>();
                tables[type].Add(row);
                return row;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="row"></param>
            public void AddXmlRow(XmlRow row)
            {
                if (!tables.ContainsKey(row.name))
                    tables[row.name] = new List<XmlRow>();
                tables[row.name].Add(row);
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="name"></param>
            /// <returns></returns>
            public XmlRow[] RowsOf(string name)
            {
                if (tables.ContainsKey(name))
                    return tables[name].ToArray();
                return new XmlRow[0];
            }

            #region Read and Write
            public void WriteXml(XmlWriter writer)
            {
                writer.WriteStartElement(name);
                for (int i = 0; i < attr.Keys.Count; i++)
                    writer.WriteAttributeString(attr.Keys[i], attr.Values[i]);

                foreach (List<XmlRow> rows in tables.Values)
                    foreach (XmlRow row in rows)
                        row.WriteXml(writer);
                if (value != null &&
                    value.Trim() != "")
                {
                    writer.WriteValue(value);
                }
                writer.WriteEndElement();
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="reader"></param>
            /// <returns></returns>
            public static XmlRow ReadXml(XmlReader reader)
            {
                XmlRow row = new XmlRow();
                row.name = reader.Name;
                if (reader.AttributeCount > 0)
                {
                    for (int i = 0; i < reader.AttributeCount; i++)
                    {
                        reader.MoveToAttribute(i);
                        row.attr.Add(reader.Name, reader.Value);

                    }
                    reader.MoveToElement();
                }
                if (!reader.IsEmptyElement)
                {
                    reader.ReadStartElement();
                    while (reader.NodeType != XmlNodeType.EndElement)
                    {
                        while (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
                            reader.Read();
                        switch (reader.NodeType)
                        {
                            case XmlNodeType.Element:
                                row.AddXmlRow(ReadXml(reader));
                                break;
                            case XmlNodeType.Text:
                                row.value = reader.ReadString();
                                break;
                            case XmlNodeType.Whitespace:
                                reader.ReadString();
                                break;
                        }

                    }

                    reader.ReadEndElement();
                }
                else
                    reader.ReadStartElement();
                return row;
            }

            #endregion
        }


        //SortedList<string, string> TopAttributes
        //{
        //    get
        //    {
        //        return attr;
        //    }
        //}

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        public XmlRow[] this[string name]
        {
            get
            {
                if (tables.ContainsKey(name))
                    return tables[name].ToArray();
                return new XmlRow[0];
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="i"></param>
        /// <returns></returns>
        public XmlRow[] this[int i]
        {
            get
            {
                if (tables.Values.Count > i && i >= 0)
                    return tables.Values[i].ToArray();
                throw new IndexOutOfRangeException();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public int Count
        {
            get
            {
                return tables.Keys.Count;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public XmlRow AddXmlRow(string type)
        {
            XmlRow row = new XmlRow();
            row.NodeName = type;
            if (!tables.ContainsKey(type))
                tables[type] = new List<XmlRow>();
            tables[type].Add(row);
            return row;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="row"></param>
        public void AddXmlRow(XmlRow row)
        {
            if (!tables.ContainsKey(row.NodeName))
                tables[row.NodeName] = new List<XmlRow>();
            tables[row.NodeName].Add(row);
        }

        #region =-[ Read and Write ]=-
        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        public void WriteXml(string path)
        {
            XmlWriter writer = XmlWriter.Create(path);
            writer.WriteStartElement("root");
            //for (int i = 0; i < attr.Count; i++)
            //    writer.WriteAttributeString(attr.Keys[i], attr.Values[i]);
            //foreach (List<XmlRow> rows in tables.Values)
            //    foreach (XmlRow row in rows)
            //        row.WriteXml(writer);
            WriteXml(writer);
            writer.WriteEndElement();
            writer.Close();

        }

        public void WriteXml(XmlWriter writer)
        {
            for (int i = 0; i < attr.Count; i++)
                writer.WriteAttributeString(attr.Keys[i], attr.Values[i]);
            foreach (List<XmlRow> rows in tables.Values)
                foreach (XmlRow row in rows)
                    row.WriteXml(writer);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        /// <param name="rootRow"></param>
        public void WriteXml(string path, string rootRow)
        {
            XmlWriter writer = XmlWriter.Create(path);
            writer.WriteStartElement(rootRow);
            //for (int i = 0; i < attr.Count; i++)
            //    writer.WriteAttributeString(attr.Keys[i], attr.Values[i]);
            //foreach (List<XmlRow> rows in tables.Values)
            //    foreach (XmlRow row in rows)
            //    {
            //        row.WriteXml(writer);
            //        //writer.WriteRaw("\n");
            //    }

            WriteXml(writer);
            writer.WriteEndElement();
            writer.Close();

        }

        #region Composed model
        /// <summary>
        /// Write XML to the string stream
        /// </summary>
        /// <param name="str"></param>
        public void WriteXml(StringBuilder str)
        {
            XmlWriter writer = XmlWriter.Create(str);
            //for (int i = 0; i < attr.Count; i++)
            //    writer.WriteAttributeString(attr.Keys[i], attr.Values[i]);
            //foreach (List<XmlRow> rows in tables.Values)
            //    foreach (XmlRow row in rows)
            //        row.WriteXml(writer);
            WriteXml(writer);

            writer.Close();

        }

        /// <summary>
        /// Read XML from XMLReader
        /// <para>Open the reader before and close it after</para>
        /// </summary>
        /// <param name="reader"></param>
        /// <returns></returns>
        static XmlHelper ReadXML(XmlReader reader)
        {
            XmlHelper row = new XmlHelper();
            reader.ReadAttributeValue();
            if (reader.AttributeCount > 0)
            {
                for (int i = 0; i < reader.AttributeCount; i++)
                {
                    reader.MoveToAttribute(i);
                    row.attr.Add(reader.Name, reader.Value);

                }
                reader.MoveToElement();
            }
            reader.ReadStartElement();
            while (reader.NodeType != XmlNodeType.EndElement && reader.ReadState != ReadState.EndOfFile)
            {
                while (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
                    reader.Read();
                switch (reader.NodeType)
                {
                    case XmlNodeType.Element:
                        row.AddXmlRow(XmlRow.ReadXml(reader));
                        break;
                    case XmlNodeType.Whitespace:
                        break;
                    case XmlNodeType.Text:
                        break;

                }
            }
            return row;
        }

        /// <summary>
        /// Read XML file from string
        /// </summary>
        /// <param name="str"></param>
        /// <returns></returns>
        public static XmlHelper ReadXmlStr(string str)
        {
            XmlReader reader = XmlReader.Create(new System.IO.StringReader(str));
            XmlHelper row = ReadXML(reader);
            reader.Close();
            return row;

            //    new XmlDatabaseHelper();
            //reader.ReadAttributeValue();
            //if (reader.AttributeCount > 0)
            //{
            //    for (int i = 0; i < reader.AttributeCount; i++)
            //    {
            //        reader.MoveToAttribute(i);
            //        row.attr.Add(reader.Name, reader.Value);

            //    }
            //    reader.MoveToElement();
            //}
            //reader.ReadStartElement();
            //while (reader.NodeType != XmlNodeType.EndElement && reader.ReadState != ReadState.EndOfFile)
            //{
            //    while (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
            //        reader.Read();
            //    switch (reader.NodeType)
            //    {
            //        case XmlNodeType.Element:
            //            row.AddXmlRow(XmlRow.ReadXml(reader));
            //            break;
            //        case XmlNodeType.Whitespace:
            //            break;
            //        case XmlNodeType.Text:
            //            break;

            //    }
            //}
            //reader.Close();
            //return row;
        }
        #endregion

        /// <summary>
        /// Read XML database from file (usually from a .xml file)
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        public static XmlHelper ReadXml(string path)
        {
            XmlReader reader = XmlReader.Create(path);
            XmlHelper row = ReadXML(reader);
            reader.Close();
            return row;

            //    new XmlDatabaseHelper();
            //reader.ReadAttributeValue();
            //if (reader.AttributeCount > 0)
            //{
            //    for (int i = 0; i < reader.AttributeCount; i++)
            //    {
            //        reader.MoveToAttribute(i);
            //        row.attr.Add(reader.Name, reader.Value);

            //    }
            //    reader.MoveToElement();
            //}
            //reader.ReadStartElement();
            //while (reader.NodeType != XmlNodeType.EndElement && reader.ReadState != ReadState.EndOfFile)
            //{
            //    while (reader.NodeType == XmlNodeType.Whitespace || reader.NodeType == XmlNodeType.Comment)
            //        reader.Read();
            //    switch (reader.NodeType)
            //    {
            //        case XmlNodeType.Element:
            //            row.AddXmlRow(XmlRow.ReadXml(reader));
            //            break;
            //        case XmlNodeType.Whitespace:
            //            break;
            //        case XmlNodeType.Text:
            //            break;

            //    }
            //}
            //reader.Close();
            //return row;
        }
        #endregion
    }
}
